#include "D3DApp.h"

extern D3DApp* gD3DApp = 0;
extern IDirect3DDevice9* gD3DDevice = 0;

LRESULT CALLBACK MainWndProc(HWND hwnd
	, UINT msg
	, WPARAM wParam
	, LPARAM lParam)
{
	if (gD3DApp != 0)
	{
		return gD3DApp->MsgProc(msg, wParam, lParam);
	}
	else
	{
		return DefWindowProc(hwnd, msg, wParam, lParam);
	}
}

D3DApp::D3DApp()
{
}

D3DApp::D3DApp(HINSTANCE hInstance, std::string winCaption
	, D3DDEVTYPE devType, DWORD requestedVP)
	: mMainWindowCaption(winCaption)
	, mhMainWindow(0)
	, mhAppInstance(hInstance)
	, mDevType(devType)
	, mRequestedVP(requestedVP)
	, mD3DObject(0)
	, pos(25.0f, 30.0f, 25.0f)
	, up(0.0f, 1.0f, 0.0f)
	, target(0.0f, 0.0f, 0.0f)
	, mAppPaused(false)
{
	srand((unsigned int)time(0));

	//standard input/ output/ error file pointers
	FILE *fpStdIn, *fpStdOut, *fpStdErr;

	// Enable run-time memory check for debug build
#if defined(DEBUG) | defined(_DEBUG)
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

	if (AllocConsole())
	{
		//Assign the stdin/ stdout/ stderr streams to the newly created console
		_tfreopen_s(&fpStdIn, _T("CONIN$"), _T("r"), stdin);
		_tfreopen_s(&fpStdOut, _T("CONOUT$"), _T("w"), stdout);
		_tfreopen_s(&fpStdErr, _T("CONOUT$"), _T("w"), stderr);
	}
#endif

	InitMainWindow();
	InitDirect3D();
}

D3DApp::~D3DApp()
{
	ReleaseCOM(mD3DObject);
	ReleaseCOM(mSprite);
	ReleaseCOM(gD3DDevice);
}

void D3DApp::InitMainWindow()
{
	WNDCLASS wc;
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = MainWndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = mhAppInstance;
	wc.hIcon = ::LoadIcon(0, IDI_APPLICATION);
	wc.hCursor = ::LoadCursor(0, IDC_ARROW);
	wc.hbrBackground = static_cast<HBRUSH>(::GetStockObject(WHITE_BRUSH));
	wc.lpszMenuName = 0;
	wc.lpszClassName = _T("Hello");

	if (!::RegisterClass(&wc))
	{
		::MessageBox(0, _T("RegisterClass - Failed"), 0, 0);
		PostQuitMessage(0);
	}

	RECT r = { 0, 0, 800, 600 };

	mhMainWindow = CreateWindow(wc.lpszClassName
		, mMainWindowCaption.c_str()
		, WS_OVERLAPPEDWINDOW
		, r.left
		, r.top
		, r.right
		, r.bottom
		, 0, 0
		, mhAppInstance
		, 0);

	if (!mhMainWindow)
	{
		::MessageBox(0, _T("CreateWindow - Failed"), 0, 0);
		PostQuitMessage(0);
	}

	::ShowWindow(mhMainWindow, SW_SHOW);
	::UpdateWindow(mhMainWindow);
}

void D3DApp::InitDirect3D()
{
	// Step 1
	mD3DObject = Direct3DCreate9(D3D_SDK_VERSION);
	if (!mD3DObject)
	{
		MessageBox(0, _T("Direct3DCreate9 - Failed"), 0, 0);
		PostQuitMessage(0);
	}

	// Step 2
	D3DDISPLAYMODE mode;
	mD3DObject->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &mode);
	HR(mD3DObject->CheckDeviceType(D3DADAPTER_DEFAULT, mDevType, mode.Format, mode.Format, true));
	HR(mD3DObject->CheckDeviceType(D3DADAPTER_DEFAULT, mDevType, D3DFMT_X8R8G8B8, D3DFMT_X8R8G8B8, false));

	// Step 3
	D3DCAPS9 caps;
	HR(mD3DObject->GetDeviceCaps(D3DADAPTER_DEFAULT, mDevType, &caps));
	DWORD devBehaviourFlags = 0;
	if (caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
	{
		devBehaviourFlags |= mRequestedVP;
	}
	else
	{
		devBehaviourFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
	}

	if (caps.DevCaps & D3DDEVCAPS_PUREDEVICE && devBehaviourFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING)
	{
		devBehaviourFlags |= D3DCREATE_PUREDEVICE;
	}

	// Step4
	mD3Dpp.BackBufferWidth = 0;
	mD3Dpp.BackBufferHeight = 0;
	mD3Dpp.BackBufferFormat = D3DFMT_UNKNOWN;
	mD3Dpp.BackBufferCount = 1;
	mD3Dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
	mD3Dpp.MultiSampleQuality = 0;
	mD3Dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	mD3Dpp.hDeviceWindow = mhMainWindow;
	mD3Dpp.Windowed = true;
	mD3Dpp.EnableAutoDepthStencil = true;
	mD3Dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
	mD3Dpp.Flags = 0;
	mD3Dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
	mD3Dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

	// Step 5
	HR(mD3DObject->CreateDevice(D3DADAPTER_DEFAULT, mDevType, mhMainWindow, devBehaviourFlags, &mD3Dpp, &gD3DDevice));

	HR(D3DXCreateSprite(gD3DDevice, &mSprite));
}

int D3DApp::Run()
{
	DirectInput di(GetAppInstance()
		, GetMainWindow(),
		DISCL_NONEXCLUSIVE | DISCL_FOREGROUND,
		DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);
	gDInput = &di;

	gTimer = new GameTimer();
	gTimer->Reset();
	gTimer->Start();

	MSG msg;
	ZeroMemory(&msg, sizeof(MSG));

	while (msg.message != WM_QUIT)
	{
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			if (mAppPaused)
			{
				Sleep(20);
				continue;
			}

			RedrawWindow(mhMainWindow, 0, 0, RDW_INVALIDATE);

			if (!GetDeviceLost())
			{
				gDInput->poll();
				gTimer->Tick();

				Update(gTimer->GetDeltaTime());
				Draw(*mSprite, mView, mProj);
			}
		}
	}
	return (int)msg.wParam;
}

void D3DApp::OnLostDevice()
{
	HR(mSprite->OnLostDevice());

	std::vector<Component*>::const_iterator it;
	for (it = Component::GetComponents()->begin();
		it != Component::GetComponents()->end();)
	{
		(*it++)->OnLostDevice();
	}
}

void D3DApp::OnResetDevice()
{
	BuildViewProjMtx();

	HR(mSprite->OnResetDevice());

	std::vector<Component*>::const_iterator it;
	for (it = Component::GetComponents()->begin();
		it != Component::GetComponents()->end();)
	{
		(*it++)->OnResetDevice();
	}
}

void D3DApp::Update(double dt)
{
	BuildViewProjMtx();

	std::vector<Component*>::const_iterator it;
	for (it = Component::GetComponents()->begin();
		it != Component::GetComponents()->end();)
	{
		(*it++)->Update(dt);
	}
}

void D3DApp::Draw(ID3DXSprite& sprite, D3DXMATRIX& view, D3DXMATRIX& proj)
{
	gD3DDevice->Clear(0, 0,
		D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
		D3DCOLOR_XRGB(255, 255, 255),
		1.0f, 0);

	std::vector<Component*>::const_iterator it;
	for (it = Component::GetComponents()->begin();
		it != Component::GetComponents()->end();)
	{
		(*it++)->Draw(sprite, view, proj);
	}
}

LRESULT D3DApp::MsgProc(UINT msg
	, WPARAM wParam
	, LPARAM lParam)
{
	static bool isMinimized = false;

	RECT clientRect = { 0 };
	::GetClientRect(mhMainWindow, &clientRect);

	switch (msg)
	{
	case WM_ACTIVATE:
		mAppPaused = LOWORD(wParam == WA_INACTIVE);
		return 0;
	case WM_SIZE:
		if (gD3DDevice)
		{
			mD3Dpp.BackBufferWidth = LOWORD(lParam);
			mD3Dpp.BackBufferHeight = HIWORD(lParam);

			if (wParam == SIZE_MINIMIZED)
			{
				mAppPaused = true;
				isMinimized = true;
			}
			else if (wParam == SIZE_MAXIMIZED)
			{
				mAppPaused = false;
				isMinimized = false;

				ResetDevice();
			}
			else if (wParam == SIZE_RESTORED)
			{
				mAppPaused = false;
				if (isMinimized && mD3Dpp.Windowed)
				{
					ResetDevice();
				}
				isMinimized = false;
			}
		}
		return 0;
	case WM_EXITSIZEMOVE:
		GetClientRect(mhMainWindow, &clientRect);
		mD3Dpp.BackBufferWidth = clientRect.right;
		mD3Dpp.BackBufferHeight = clientRect.bottom;
		ResetDevice();
		return 0;
	case WM_CLOSE:
		DestroyWindow(mhMainWindow);
		return 0;
	case WM_KEYDOWN:
		if (wParam == VK_ESCAPE)
			DestroyWindow(mhMainWindow);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return ::DefWindowProcA(mhMainWindow, msg, wParam, lParam);
}

void D3DApp::ResetDevice()
{
	OnLostDevice();
	HR(gD3DDevice->Reset(&mD3Dpp));
	OnResetDevice();
}

bool D3DApp::GetDeviceLost()
{
	HRESULT hr = gD3DDevice->TestCooperativeLevel();
	switch (hr)
	{
	case D3DERR_DEVICELOST:
		Sleep(20);
		return true;
	case D3DERR_DRIVERINTERNALERROR:
		MessageBox(0, "Internal Driver error... Exiting", 0, 0);
		PostQuitMessage(0);
		return true;
	case D3DERR_DEVICENOTRESET:
		ResetDevice();
		return false;
	default:
		return false;
	}
}

void D3DApp::BuildViewProjMtx()
{
	D3DXMatrixLookAtLH(&mView, &pos, &target, &up);
	D3DXMatrixPerspectiveFovLH(&mProj,
		D3DX_PI * 0.25f,
		(float)mD3Dpp.BackBufferWidth / (float)mD3Dpp.BackBufferHeight,
		1.0f, 5000.0f);

	//D3DXMatrixOrthoLH(&proj,
	//	(float)mD3Dpp.BackBufferWidth,
	//	(float)mD3Dpp.BackBufferHeight, 1.0f, 5000.0f);
}

